﻿#region 참조 네임스페이스

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using Skying.Diagnostics.Logger.Arbitration;
using Skying.Diagnostics.Logger.Coodinator;
using Skying.Diagnostics.Logger.Entry;

#endregion

namespace Skying.Diagnostics.Logger.Configuration
{
	public class Configurator
	{
		#region 변수

		/// <summary>
		/// 로거 환경 설정 정보로 어플리케이션의 AssemblyInfo로부터 로드된다.
		/// </summary>
		private static LoggerSettings _loggerSettings;

		/// <summary>
		/// 로거가 문제없이 설치됐는지 여부를 나타낸다.
		/// </summary>
		private readonly bool _installed;

		/// <summary>
		/// 로그 엔트리 목록을 나타낸다.
		/// </summary>
		private IList<LogEntry> _logEntries;

		/// <summary>
		/// 루트 Arbitration을 나타낸다.
		/// </summary>
		private Arbitration.Root _rootArbitration;

		/// <summary>
		/// 로그 Arbitration 목록을 나타낸다.
		/// </summary>
		private IList<Arbitration.Arbitration> _logArbitrations;

		#endregion

		#region 프로퍼티

		/// <summary>
		/// 로거가 문제없이 설치됐는지 여부를 가져온다.
		/// </summary>
		public bool Installed
		{
			get
			{
				return this._installed;
			}
		}

		/// <summary>
		/// 로그 엔트리 목록을 가져온다.
		/// </summary>
		public IList<LogEntry> LogEntries
		{
			get
			{
				return this._logEntries;
			}
		}

		/// <summary>
		/// 루트 Arbitration을 가져온다.
		/// </summary>
		public Root RootArtbitration
		{
			get
			{
				return this._rootArbitration;
			}
		}

		/// <summary>
		/// 로그 Arbitration 목록을 가져온다.
		/// </summary>
		public IList<Arbitration.Arbitration> LogArbitrations
		{
			get
			{
				return this._logArbitrations;
			}
		}

		#endregion

		#region 생성자/소멸자

		static Configurator()
		{
			// 로거 설정 정보를 가져온다.
			_loggerSettings = GetLoggerSettings();
		}

		internal Configurator()
		{
			try
			{
				this._logEntries = new List<LogEntry>();

				this._logArbitrations = new List<Arbitration.Arbitration>();

				bool isExistConfigurationFile = false;

				// 환경 설정 파일을 읽어들인다.
				this.ReadConfigurationFile(out isExistConfigurationFile);

				// 환경 설정 파일이 존재하지 않는 경우 기본 설정을 한 뒤에 환경 설정 파일을 저장한다. 그러므로 별다른 오류 없이 기본 설정을 가지고 로깅을 실행한다.
				if (!isExistConfigurationFile)
				{
					ConsoleLogEntry consoleLogEntry = new ConsoleLogEntry("DefaultConsoleLog", new LogCoodinator(), true);
					FileLogEntry fileLogEntry = new FileLogEntry("DefaultFileLog", new LogCoodinator());

					this._logEntries.Add(consoleLogEntry);
					this._logEntries.Add(fileLogEntry);

					this._rootArbitration = new Root(LogSeverity.Debug, new List<string> { consoleLogEntry.EntryName, fileLogEntry.EntryName });

					Arbitration.Arbitration arbitration = new Arbitration.Arbitration("System", LogSeverity.Information, new List<string> { consoleLogEntry.EntryName, fileLogEntry.EntryName });

					this._logArbitrations.Add(arbitration);

					this.WriteConfigurationFile();
				}

				this._installed = true;
			}
			catch (Exception error)
			{
				this._installed = false;

				Console.WriteLine("[Logger] Logger configuration failed. Reason is the follow:");
				Console.WriteLine("[Logger] Reason : {0}", error.Message);

				if (string.IsNullOrEmpty(error.StackTrace))
				{
					Console.WriteLine("[Logger] Details : {0}", error.StackTrace);
				}
			}
		}

		#endregion

		#region 로그 설정 분석

		/// <summary>
		/// 어플리케이션의 어셈블리 정보에서 <see cref="Skying.Diagnostics.Logger.Configuration.LoggerSettings"/> 객체를 가져온다.
		/// </summary>
		/// <returns>
		/// <para>어플리케이션의 어셈블리 정보에 설정되어 있는 <see cref="Skying.Diagnostics.Logger.Configuration.LoggerSettings"/> 객체가 반환된다.</para>
		/// </returns>
		private static LoggerSettings GetLoggerSettings()
		{
			LoggerSettings loggerSettings = null;

			object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(LoggerSettings), true);

			if (attributes.Length > 0 && attributes[0] is LoggerSettings)
			{
				loggerSettings = attributes[0] as LoggerSettings;
			}
			else
			{
				loggerSettings = new LoggerSettings();
			}

			return loggerSettings;
		}

		#endregion

		#region 환경 설정 파일 읽기/쓰기

		/// <summary>
		/// 환경 설정 파일을 읽어 로거 정보를 설정한다.
		/// </summary>
		/// <param name="isExist">환경 설정 파일의 존재 여부 정보를 참조한다.</param>
		private void ReadConfigurationFile(out bool isExist)
		{
			isExist = true;

			if (!File.Exists(_loggerSettings.ConfigFilePath))
			{
				isExist = false;

				return;
			}

			using (FileStream stream = new FileStream(_loggerSettings.ConfigFilePath, FileMode.Open, FileAccess.Read))
			{
				XmlSerializer serializer = new XmlSerializer(typeof(Configuration));

				Configuration configuration = serializer.Deserialize(stream) as Configuration;

				if (configuration.Entries != null && configuration.Entries.Length > 0)
				{
					foreach (ConfigurationEntry entry in configuration.Entries)
					{
						if (string.IsNullOrEmpty(entry.Name)) continue;

						LogEntry logEntry = null;

						LogCoodinator coodinator = null;

						if (entry.Coodinator == null)
						{
							coodinator = new LogCoodinator();
						}
						else
						{
							coodinator = new LogCoodinator(entry.Coodinator.Pattern);
						}

						switch (entry.Type)
						{
							case "Skying.Diagnostics.Logger.Entry.ConsoleLogEntry":

								logEntry = new ConsoleLogEntry(entry.Name, coodinator, entry.Colored);

								break;

							case "Skying.Diagnostics.Logger.Entry.FileLogEntry":

								FileRollingCriterion rollingCriterion = FileRollingCriterion.Date;

								if (!Enum.TryParse<FileRollingCriterion>(entry.RollingCriterion, out rollingCriterion)) continue;

								logEntry = new FileLogEntry(entry.Name, coodinator, entry.LogPath, entry.Rolling, rollingCriterion, entry.DateTimeFormat, entry.FileMaximumSize);

								break;
						}

						this._logEntries.Add(logEntry);
					}

					if (configuration.Arbitrations.Root == null)
					{
						throw new InvalidOperationException("Logger required Root Arbitration. Please set it in configuration file.");
					}

					List<string> rootArbitrationEntries = new List<string>();

					foreach (ConfigurationArbitrationsRootEntriesRef item in configuration.Arbitrations.Root.EntriesRef)
					{
						rootArbitrationEntries.Add(item.EntryName);
					}

					LogSeverity rootSeverity = LogSeverity.Off;

					if (!Enum.TryParse<LogSeverity>(configuration.Arbitrations.Root.Severity, out rootSeverity)) return;

					this._rootArbitration = new Root(rootSeverity, rootArbitrationEntries);
						
					foreach (ConfigurationArbitrationsArbitration arbitration in configuration.Arbitrations.Arbitration)
					{
						if (string.IsNullOrEmpty(arbitration.Namespace)) continue;

						LogSeverity severity = LogSeverity.Off;

						if (!Enum.TryParse<LogSeverity>(arbitration.Severity, out severity)) continue;

						List<string> arbitrationEntries = new List<string>();

						foreach (ConfigurationArbitrationsArbitrationEntriesRef item in arbitration.EntriesRef)
						{
							arbitrationEntries.Add(item.EntryName);
						}

						Arbitration.Arbitration logArbitration = new Arbitration.Arbitration(arbitration.Namespace, severity, arbitrationEntries);

						this._logArbitrations.Add(logArbitration);
					}
				}
			}
		}

		/// <summary>
		/// 로거 정보를 환경 설정 파일로 저장한다.
		/// </summary>
		private void WriteConfigurationFile()
		{
			Configuration configuration = new Configuration();

			configuration.Entries = new ConfigurationEntry[this._logEntries.Count];

			for (int index = 0; index < this._logEntries.Count; index++)
			{
				LogEntry logEntry = this._logEntries[index];

				ConfigurationEntry entry = new ConfigurationEntry();

				if (logEntry is ConsoleLogEntry)
				{
					ConsoleLogEntry consoleLogEntry = logEntry as ConsoleLogEntry;

					entry.Name = consoleLogEntry.EntryName;
					entry.Type = typeof(ConsoleLogEntry).FullName;
					entry.Colored = consoleLogEntry.IsColored;
				}
				else if (logEntry is FileLogEntry)
				{
					FileLogEntry fileLogEntry = logEntry as FileLogEntry;

					entry.Name = fileLogEntry.EntryName;
					entry.Type = typeof(FileLogEntry).FullName;
					entry.LogPath = fileLogEntry.LogPath;
					entry.Rolling = fileLogEntry.IsRolling;
					entry.RollingCriterion = fileLogEntry.RollingCriterion.ToString();
				}

				entry.Coodinator = new ConfigurationEntryCoodinator() { Pattern = logEntry.Coodinator.Pattern };

				configuration.Entries[index] = entry;
			}

			configuration.Arbitrations = new ConfigurationArbitrations()
			{
				Arbitration = new ConfigurationArbitrationsArbitration[this._logArbitrations.Count],
				Root = new ConfigurationArbitrationsRoot() { Severity = this._rootArbitration.LogSeverity.ToString(), EntriesRef = new ConfigurationArbitrationsRootEntriesRef[this._rootArbitration.Entries.Count] }
			};

			for (int index = 0; index < configuration.Arbitrations.Root.EntriesRef.Length; index++)
			{
				configuration.Arbitrations.Root.EntriesRef[index] = new ConfigurationArbitrationsRootEntriesRef() { EntryName = this._rootArbitration.Entries[index] };
			}

			for (int index1 = 0; index1 < this._logArbitrations.Count; index1++)
			{
				Arbitration.Arbitration logArbitration = this._logArbitrations[index1];

				ConfigurationArbitrationsArbitration arbitration = new ConfigurationArbitrationsArbitration();

				arbitration.Namespace = logArbitration.Namespace;
				arbitration.Severity = logArbitration.LogSeverity.ToString();

				arbitration.EntriesRef = new ConfigurationArbitrationsArbitrationEntriesRef[configuration.Entries.Length];

				for (int index2 = 0; index2 < arbitration.EntriesRef.Length; index2++)
				{
					ConfigurationEntry entry = configuration.Entries[index2];

					arbitration.EntriesRef[index2] = new ConfigurationArbitrationsArbitrationEntriesRef() { EntryName = entry.Name };
				}

				configuration.Arbitrations.Arbitration[index1] = arbitration;
			}

			using (FileStream stream = new FileStream(_loggerSettings.ConfigFilePath, FileMode.Create, FileAccess.ReadWrite))
			{
				XmlSerializer serializer = new XmlSerializer(typeof(Configuration));

				serializer.Serialize(stream, configuration);
			}
		}

		#endregion
	}
}